/*
 * channelManager.cpp
 *
 *  Created on: Mar 8, 2011
 *      Author: B.Jones
 *
 *  Description:
 *
 *  This code handles communication functionality between client and server.
 *  Put it all here basically to avoid code repetition in server and client classes
 *
 */

#include "channelManager.h"
#include "comms.h"
#include "fileTransferer.h"
#include <sstream>
#include <iostream>
#include <openssl/blowfish.h>
#include <cassert>

pthread_mutex_t channelManager::sendMutex = PTHREAD_MUTEX_INITIALIZER;


channelManager::channelManager(){pid=0;}

void
channelManager::sendMessage(std::string str, int link_id)
{
	char* temp= (char*)str.c_str();
	int length = strlen(temp);

	char temp_[length+1];
	strncpy(temp_,temp,length);
	temp_[length]='\0';


	//std::cout<<"Message to send: "<<str<<std::endl;

	// first we generate a unique initialisation vector and send this along first
	comms::encryptor->sendIvec(comms::all_com_params[link_id].sockChat,0);

	// next we perform encryption of message
	comms::encryptor->blowfish(reinterpret_cast<unsigned char*>(temp_),length, BF_ENCRYPT);

	// finally we send the encrypted message
	send(comms::all_com_params[link_id].sockChat,temp_,length,0);
}

/**
 * Starts threads pertaining to receiving chat data and file receive data
 */
void
channelManager::startThreads(int link_id)
{

	//int sockChat = comms::all_com_params[link_id].sockChat;
	//int sockFile_protocol = comms::all_com_params[link_id].sockFile_protocol;

	pthread_create(new pthread_t, NULL,&channelManager::chatReceive, new accessHelper(this,(void*)pid,link_id ));
	pid++;
	pthread_create(new pthread_t, NULL,&channelManager::fileReceive, new accessHelper(this,(void*)pid,link_id));
	pid++;
}

void
channelManager::parseChatReception(int connection,const char* buffer)
{
	if(strlen(buffer)>0)
	{
		std::cout<<"buffer: "<<buffer<<std::endl;
		if(buffer[0]=='C') // chat mode
		{
			eventStore es;
			es.message = std::string(buffer+1); // skip protocol character
			es.tag = comms::all_com_params[connection].peerNick;
			es.type=2;
			pthread_mutex_lock(&listener::mutex);
			listener::eventQueue.push(es);
			pthread_mutex_unlock(&listener::mutex);

			// if we're a server, we need to relay the message to all other clients
			// we will be server when we have more than 1 connection, hence there
			// is no danger of the following loops being entered into if we're a client
			for(int r = 0;r<connection;r++)
			{
				// append an 'R' to front of message (indicates relayed chat -- protocol)
				std::stringstream ss;
				ss<<"R"<<connection<<'\n'<<es.message;
				std::string ss_str(ss.str());
				pthread_mutex_lock(&channelManager::sendMutex);
				sendMessage(ss_str,r);
				pthread_mutex_unlock(&channelManager::sendMutex);
			}
			for(int r = connection+1;r<comms::all_com_params.size();r++)
			{
				// append an 'R' to front of message (indicates relayed chat -- protocol)
				std::stringstream ss;
				ss<<"R"<<connection<<'\n'<<es.message;
				std::string ss_str(ss.str());
				pthread_mutex_lock(&channelManager::sendMutex);
				sendMessage(ss_str,r);
				pthread_mutex_unlock(&channelManager::sendMutex);
			}

		}
		else if(buffer[0]=='P') // new peer has joined
		{
			eventStore es;
			std::string extractString(buffer+1);
			std::stringstream ss;
			int counter=0;
			while(extractString.at(counter)!='\n')
			{
				ss<<extractString.at(counter);
				counter++;
			}
			int resultId = atoi(ss.str().c_str());

			std::string peerString(buffer+counter+1);

			std::stringstream ss_;
			ss_<<"New peer with name "<<peerString<<" (ID: "<<resultId<<") has joined the server";
			es.message = std::string(ss_.str()); // skip protocol character
			es.tag = "STATUS";
			es.type=7;
			es.extra=std::string(peerString);
			pthread_mutex_lock(&listener::mutex);
			listener::eventQueue.push(es);
			pthread_mutex_unlock(&listener::mutex);

			// Need to create new peer info struct here!
			peer_info pi;
			pi.peerId=resultId;
			pi.peerNick=std::string(peerString);
			if(resultId>comms::all_peer_infos.size())
			{
				while(comms::all_peer_infos.size()<resultId)
				{
					peer_info pi;
					pi.peerId=-1;
					pi.peerNick=std::string("_filler_");
					comms::all_peer_infos.push_back(pi);
				}
				comms::all_peer_infos.push_back(pi);
			}
			else if(resultId<comms::all_peer_infos.size())
			{
				comms::all_peer_infos[resultId].peerId=resultId;
				comms::all_peer_infos[resultId].peerNick=std::string(peerString);
			}
			else
			{
				std::cout<<"same size, pushing back pi"<<std::endl;
				comms::all_peer_infos.push_back(pi);
			}
		}
		else if(buffer[0]=='R') // relaying message from another peer
		{

			eventStore es;
			std::string extractString(buffer+1);
			std::stringstream ss;
			int counter=0;
			while(extractString.at(counter)!='\n')
			{
				ss<<extractString.at(counter);
				counter++;
			}
			int resultId = atoi(ss.str().c_str());

			std::cout<<resultId<<std::endl;

			std::string peerString(comms::all_peer_infos[resultId].peerNick);
			es.message = std::string(buffer+counter+1); // skip protocol character
			es.tag = std::string(peerString);//comms::all_com_params[connection].peerNick;
			es.type=2;
			pthread_mutex_lock(&listener::mutex);
			listener::eventQueue.push(es);
			pthread_mutex_unlock(&listener::mutex);
		}
	}
}

void
*channelManager::chatReceive(void *tid)
{

	accessHelper* ah = static_cast<accessHelper*>(tid);

	int connection = ah->link_id;

	channelManager *This=ah->This;

	std::string peerNick(comms::all_com_params[connection].peerNick);
	int sockChat = comms::all_com_params[connection].sockChat;


	// note -- need to make the receive non-blocking so that chat window
	// doesn't hang while waiting for earlier peer connections to say something
	// Of course, we could alternatively set up a thread for each chatter instead perhaps? Yes
	// we did this instead..

	while(1)
	{
		//for(int connection=0;connection<comms::all_com_params.size();connection++)
		{
			int bufferSize=4096;
			char buffer[bufferSize];
			bzero(buffer,bufferSize);

			// listener_ for incoming chat data; n stores number of bytes transferred
			// -------------------------------------------------------------------
			//std::cout<<"About to recieve ivec"<<std::endl;
			comms::encryptor->receiveIvec(sockChat,0);

			//std::cout<<"received ivec: "<<enc->ivec<<std::endl;
			int n = recv(sockChat,buffer,bufferSize,0);
			comms::encryptor->blowfish(reinterpret_cast<unsigned char*>(buffer),n,BF_DECRYPT);
			buffer[n]='\0';
			// process the received data
			channelManager::parseChatReception(connection,buffer);
		}
	}

	pthread_exit(NULL);
}

void
channelManager::initiateFileTransfer(int link_id,const char* filePath, const char* filename)
{

	pthread_create(new pthread_t, NULL,&channelManager::send_fileT, new rtd(this,(void*)pid,link_id,filename,filePath,false));
	pid++;

}

void
channelManager::initiateFileReceive(int link_id,const char* savePath,bool yesno)
{

	std::cout<<"In initiate file receive, savePath is "<<savePath<<std::endl;
	pthread_create(new pthread_t, NULL,&channelManager::receive_fileT, new rtd(this,(void*)pid,link_id,
			receiveString,std::string(savePath),yesno));
	pid++;
}

void
*channelManager::receive_fileT(void *args)
{

	rtd* rtd_ = static_cast<rtd*>(args);
	int link_id = rtd_->link_id;

	std::string fname(rtd_->fname);
	std::string path(rtd_->absolutePath);
	std::cout<<"path in receive_fileT: "<<path<<std::endl;

	int sockFile_receive=comms::all_com_params[link_id].sockFile_receive;
	int sockFile_protocol=comms::all_com_params[link_id].sockFile_protocol;
	bool yesno = rtd_->yesno;

	// receive file
	if(yesno) // we are happy to receive file ('OK')
	{
		// send ack
		int n = send(sockFile_receive,"OK",strlen("OK"),0);

		std::cout<<"We are happy to receive file "<<fname<<std::endl;
		std::cout<<"absolute path: "<<path.c_str()<<std::endl;
		comms::ft->receive_file(sockFile_receive,path.c_str());

	}
	else // we are not happy to receive file ('KO')
	{
		int n = send(sockFile_receive,"KO",strlen("KO"),0);
	}

	pthread_exit(NULL);
}

// A thread for sending a file.
void
*channelManager::send_fileT(void* tid)
{

	//encap::continueSending = true;

	rtd* rtd_ = static_cast<rtd*>(tid);
	int link_id=rtd_->link_id;
	channelManager* This = rtd_->This;
	std::string path(rtd_->absolutePath);
	std::string fname(rtd_->fname);
	int sockFile_send=comms::all_com_params[link_id].sockFile_send;
	int sockFile_protocol=comms::all_com_params[link_id].sockFile_protocol;

	/*
	std::cout<<"path: "<<path<<std::endl;
	std::cout<<"fname: "<<fname<<std::endl;
	std::cout<<"sockFile_send: "<<sockFile_send<<std::endl;
	std::cout<<"sockFile_protocol: "<<sockFile_protocol<<std::endl;
	*/

	// tell receiver we want to send a file
	char keyword[10];
	bzero(keyword,10);
	strncpy(keyword,"FILE",4);
	int n = send(sockFile_protocol,keyword,10,0);

	char okBuffer[2];
	bzero(okBuffer,2);

	// new ivec
	comms::encryptor->sendIvec(sockFile_protocol,0);

	// next we perform encryption of message
	char fname_[64];
	strncpy(fname_,fname.c_str(),fname.length());
	comms::encryptor->blowfish(reinterpret_cast<unsigned char*>(fname_),fname.length(), BF_ENCRYPT);

	//fname[strlen(fname)]='\0';
	//fname_[strlen(fname_)]='\0';

	// now send file name
	n=send(sockFile_protocol,fname_,fname.length(),0);

	// recieve ack to see if user is happy with wanting to receive file
	// (will be 'KO' if user not ok with receiving file)
	n = recv(sockFile_send,okBuffer,2,0);

	// now finally send file if receiver is happy to do so
	if(okBuffer[0]=='O'&&okBuffer[1]=='K')
	{
		std::cout<<"Peer is happy to receive file "<<fname<<std::endl;
		comms::ft->send_file(sockFile_send,path.c_str());
	}
	else if(okBuffer[0]=='K'&&okBuffer[1]=='O')
	{
		eventStore es;
		es.message = "Peer declined file";
		es.tag = "STATUS";
		es.type=3;
		pthread_mutex_lock(&listener::mutex);
		listener::eventQueue.push(es);
		pthread_mutex_unlock(&listener::mutex);
	}

	pthread_exit(NULL);
}

// operates using sock file protocol
void
*channelManager::fileReceive(void *tid)
{
	accessHelper* ah = static_cast<accessHelper*>(tid);
	channelManager* This = ah->This;
	int link_id = ah->link_id;
	int sockFile_protocol = comms::all_com_params[link_id].sockFile_protocol;
	std::string peerNick(comms::all_com_params[link_id].peerNick);

	int connection = ah->link_id;

	// file handling stuff. If keyword 'FILE' is received, then starts
	// file receiving procedures. Also initiates file send/recv cancelling
	// is user wishes to cancel a given transfer
	while(1)
	{
		//for(int connection=0;connection<comms::all_com_params.size();connection++)
		{
			int sockFile_protocol = comms::all_com_params[connection].sockFile_protocol;
			std::string peerNick(comms::all_com_params[connection].peerNick);

			int bufferSize=10;
			char buffer[bufferSize];
			bzero(buffer,bufferSize);
			int n = recv(sockFile_protocol,buffer,bufferSize,0);


			if(buffer[0]=='F'&&buffer[1]=='I'&&buffer[2]=='L'&&buffer[3]=='E')
			{
				// receive ivec
				//unsigned char ivec[20];
				comms::encryptor->receiveIvec(sockFile_protocol,0);

				// receive fname
				char fname[64]={0};
				n = recv(sockFile_protocol,fname,64,0);
				std::cout<<"received fname...decrypting fname"<<std::endl;
				comms::encryptor->blowfish(reinterpret_cast<unsigned char*>(fname),strlen(fname),BF_DECRYPT);
				reinterpret_cast<char*>(fname);

				char pus[64]={'0'};
				//saveName[strlen(saveName)-1]='\0';
				strncpy(pus,fname,strlen(fname));
				pus[strlen(fname)]='\0';
				// send an event indicating name of file. Will use the id 4 to know a file
				// is trying to be sent

				eventStore es;
				es.message = std::string(pus);
				es.tag = std::string(peerNick);
				es.type=4;
				pthread_mutex_lock(&listener::mutex);
				listener::eventQueue.push(es);
				pthread_mutex_unlock(&listener::mutex);

				This->receiveString.assign(pus);

			}
			//cancel sending file

			/*
			else if(buffer[0]=='C'&&buffer[1]=='A'&&buffer[2]=='N'&&buffer[3]=='C'&&buffer[4]=='E'&&buffer[5]=='L'&&
			   buffer[6]=='S'&&buffer[7]=='E'&&buffer[8]=='N'&&buffer[9]=='D')
			{

				encap::continueSending=false;
				int n = send(sockFile,"CANCELRECV",strlen("CANCELRECV"),0);
			}
			//cancel receiving file
			else if(buffer[0]=='C'&&buffer[1]=='A'&&buffer[2]=='N'&&buffer[3]=='C'&&buffer[4]=='E'&&buffer[5]=='L'&&
			   buffer[6]=='R'&&buffer[7]=='E'&&buffer[8]=='C'&&buffer[9]=='V')
			{
				encap::continueReceiving=false;

			}*/

		}

	}

	pthread_exit(NULL);
}

